(function(fn) {
  
    
    if (typeof require === 'function' && typeof module === 'object' && module) //兼容cmd
    {
        fn(module.exports);
    }
    else if (typeof define === 'function' && define.amd) //兼容amd
    {  
        define(function (require, exports, module) {

            fn(exports);
        });
    }
    else //普通浏览器环境
    {
        fn(window);  
    }


})(function (exports) {


    'use strict'


    // 注册的Spirte类型集合
    var registry_sprites = Object.create(null);


    // 定义属性方法
    var defineProperty = Object.defineProperty;


    // 类继承方法
    Object.extend = function (fn) {
    
        var base = this.prototype,
            prototype = Object.create(base);
    
        function Class() {
    
            var ctor;
    
            if (ctor = Class.ctor)
            {
                ctor.apply(this, arguments);
            }
        };
    
        Class.prototype = prototype; 
        Class.superclass = this;
        Class.base = base;
    
        prototype.Class = prototype.constructor = Class;
    
        fn.call(prototype, Class, base);
    
        Class.extend = this.extend;
    
        if (!Class.ctor && (fn = this.ctor))
        {
            Class.ctor = fn;
        }
        
        if (!Class.registry && (fn = this.registry))
        {
            Class.registry = fn;
        }
    
        return Class;
    };
    



    // 贝塞尔曲线函数集
    var bezier = (function () {


        var fn = Object.create(null);


        // 获取带控制点的贝塞尔曲线坐标集合
        fn.controls = function (points) {

            var list = [],
                length = points.length,
                loop = (length >> 1) - 1,
                k = 0.25,  // 曲线因子(影响控制线长度)
                index,
                x1,
                y1,
                x2,
                y2;

            for (var i = 0; i < loop; i++)
            {
                index = i << 1;

                list.push(points[index], points[index + 1]);

                // 第一个控制点
                if (index > 1)
                {
                    x1 = points[index] + (points[index + 2] - points[index - 2]) * k;
                    y1 = points[index + 1] + (points[index + 3] - points[index - 1]) * k;
                }
                else // 开始的控制点特殊处理
                {
                    x1 = points[0] + (points[2] - points[0]) * k;
                    y1 = points[1] + (points[3] - points[1]) * k;
                }

                // 第二个控制点
                if (index < points.length - 4)
                {
                    x2 = points[index + 2] - (points[index + 4] - points[index]) * k;
                    y2 = points[index + 3] - (points[index + 5] - points[index + 1]) * k;
                }
                else // 后面两个节点特殊处理
                {                
                    index = points.length - 2;

                    x2 = points[index] - (points[index] - points[index - 2]) * k;
                    y2 = points[index + 1] - (points[index + 1] - points[index - 1]) * k;
                }

                list.push(x1, y1, x2, y2);
            }

            list.push(points[length - 2], points[length - 1]);

            return list;
        };
        

        // 获取贝塞尔曲线指定位置的坐标
		fn.segments = function (points, index, count, list, first) {

			var x1 = points[index++],
				y1 = points[index++],
				x2 = points[index++],
                y2 = points[index++],
                x3 = points[index++],
                y3 = points[index++],
                x4 = points[index++],
                y4 = points[index],
				cx = 3 * (x2 - x1),
				cy = 3 * (y2 - y1),
				bx = 3 * (x3 - x2) - cx,
				by = 3 * (y3 - y2) - cy,
				ax = x4 - x1 - cx - bx,
                ay = y4 - y1 - cy - by,
                t = 1 / count;

            list = list || [];

            if (first !== false)
            {
                list.push(x1, y1);
            }

            for (var i = 1; i < count; i++)
            {
                var t1 = t * i,
                    t2 = t1 * t1,
                    t3 = t2 * t1;

                list.push(
                    ax * t3 + bx * t2 + cx * t1 + x1, 
                    ay * t3 + by * t2 + cy * t1 + y1);
            }

            list.push(x4, y4);

            return list;
        };
        

        // 按指定段长近似拆分贝塞尔曲线(注：不是准确的段长)
        fn.split = function (points, size) {

            var sqrt = Math.sqrt,
                list = [points[0], points[1]],
                index = 0,
                length = points.length - 2;

            // 先计算出曲线上的相关坐标点
            while (index < length)
            {
                // 估算出大概分段数
                var dx = points[index + 6] - points[index],
                    dy = points[index + 7] - points[index + 1],
                    distance = sqrt(dx * dx + dy * dy),
                    segments = distance / size | 0;

                fn.segments(points, index, segments, list, false);
                index += 6;
            }

            return list;
        };


        return fn;


    })();
    


    // 命中检测函数集
    var hitTest = (function () {


        var fn = Object.create(null);

        var sqrt = Math.sqrt;        


        // 检测指定的点是否在矩形内
        fn.rect = function (x, y, left, top, width, height) {

            return x >= left && y >= top && x <= left + width && y <= top + height;
        };


        // 检测指定的点是否在圆形内
        fn.cycle = function (x, y, left, top, radius) {

            x -= left;
            y -= top;

            return sqrt(x * x + y * y) <= radius;
        };


        // 检测指定的坐标是否在多边形中
        // 使用水平射线法, 与向右的水平射线相交的节点数为单数则表示点在多边形内
        fn.polygon = function (x, y, points, index, length) {

            if (index === void 0)
            {
                index = 0;
                length = points.length;
            }

            var flag = false,
                x1 = points[length - 2] | 0,
                y1 = points[length - 1] | 0,
                x2,
                y2;

            if (x === x1 && y === y1)
            {
                return true;
            }

            while (index < length)
            {
                x2 = points[index++] | 0;
                y2 = points[index++] | 0;

                if (x === x2 && y === y2)
                {
                    return true;
                }

                // 判断线段两端点的y坐标是否在射线两侧
                if (y1 >= y && y2 <= y || y1 <= y && y2 >= y)
                {
                    // 线段上与射线Y坐标相同的点的X坐标
                    x1 += (y - y1) * (x2 - x1) / (y2 - y1) | 0;

                    if (x1 === x)
                    {
                        return true;
                    }

                    if (x1 > x)
                    {
                        flag = !flag;
                    }
                }

                x1 = x2;
                y1 = y2;
            }

            return flag;
        };


        // 检测指定的点是否接近直线
        fn.line = function (x, y, points, offset) {

            var PI = Math.PI / 2,
                sqrt = Math.sqrt,
                acos = Math.acos,
                sin = Math.sin,
                index = 2,
                length = points.length,
                x1 = points[0],
                y1 = points[1],
                x2,
                y2,
                ab,
                ax,
                bx,
                any;
            
            while (index < length)
            {
                x2 = points[index++];
                y2 = points[index++];

                ax = sqrt((any = x - x1) * any + (any = y - y1) * any);

                if (ax <= offset)
                {
                    return true;
                }

                bx = sqrt((any = x - x2) * any + (any = y - y2) * any);

                if (bx <= offset)
                {
                    return true;
                }

                ab = sqrt((any = x2 - x1) * any + (any = y2 - y1) * any);

                // 通过余弦定理求角度,如果点与线段的端点小于90度,则垂线为最短距离,否则对应的边线为最短距离
                if ((any = acos((ax * ax + ab * ab - bx * bx) / (2 * ax * ab))) < PI &&
                    acos((bx * bx + ab * ab - ax * ax) / (2 * bx * ab)) < PI)
                {
                    // 检测垂线距离
                    if (ax * sin(any) <= offset)
                    {
                        return true;
                    }
                }

                x1 = x2;
                y1 = y2;
            }

            return false;
        };


        // 检测指定的点是否在贝塞乐曲线上
        fn.bezier = function (x, y, points) {

            var index = 0,
                length = points.length,
                polygon = fn.polygon,
                split = bezier.split,
                line = fn.line;

            while (index < length)
            {
                var list = split(points, 20);

                if (line(x, y, list, 6))
                {
                    return true;
                }

                index += 6;
            }

            return false;
        };


        return fn;


    })();


    
    /**
     * 精灵基类
     */
    var Sprite = Object.extend(function (Class) {
    
    

        var id = 1;


        /**
         * Id
         */
        defineProperty(this, 'id', {

            get: function () {

                return this.__id || (this.__id = id++);
            }
        });


        /**
         * 文字内容
         */
        defineProperty(this, 'text', {

            get: function () {

                return this.__text || this.__text_ || '';
            },

            set: function (value) {

                this.__text = '' + value;
            }
        });


        /**
         * 边框线宽度
         */
        defineProperty(this, 'borderWidth', {

            get: function () {

                return this.__borderWidth || 2;
            },

            set: function (value) {

                this.__borderWidth = value | 0;
            }
        });


        /**
         * 边框颜色
         */
        defineProperty(this, 'borderColor', {

            get: function () {

                return this.__borderColor || 'transparent';
            },

            set: function (value) {

                this.__borderColor = '' + value;
            }
        });


        /**
         * 背景颜色
         */
        defineProperty(this, 'backgroundColor', {

            get: function () {

                return this.__backgroundColor || '';
            },

            set: function (value) {

                this.__backgroundColor = '' + value;
            }
        });


        /**
         * 字体颜色
         */
        defineProperty(this, 'color', {

            get: function () {

                return this.__color || 'white';
            },

            set: function (value) {

                this.__color = '' + value;
            }
        });


        /**
         * 是否可见
         */
        defineProperty(this, 'visible', {

            get: function () {

                return this.__visible === false ? false : true;
            },

            set: function (value) {

                this.__visible = !!value;
            }
        });



        this.render = function (context, x, y) {
    
        };


        this.renderSelected = function (context, x, y) {


        };


        this.hitTest = function (x, y, left, top) {

        };



        this.location = function (frame) {

            return frame.points[this.id].slice(0);
        };


        this.dragMove = function (start, distanceX, distanceY, frame) {

            var points = frame.points[this.id];

            points[0] = start[0] + distanceX;
            points[1] = start[1] + distanceY;

            return points;
        };



        this.serialize = function (array) {

            var any;

            array.push('"id":', this.id, ',"sprite":"', this.typeName, '"');

            if (any = this.__text)
            {
                array.push(',"text":"', any, '"');
            }

            if (any = this.__borderWidth)
            {
                array.push(',"borderWidth":', any);
            }

            if (any = this.__borderColor)
            {
                array.push(',"borderColor":"', any, '"');
            }

            if (any = this.__backgroundColor)
            {
                array.push(',"backgroundColor":"', any, '"');
            }

            if (any = this.__color)
            {
                array.push(',"color":"', any, '"');
            }

            if (this.__visible === false)
            {
                array.push(',"visible":false');
            }
        };


        this.deserialize = function (data) {

            for (var name in data)
            {
                switch (name)
                {
                    case 'id':
                        if ((this.__id = data[name]) >= id)
                        {
                            id++;
                        }
                        break;

                    case 'sprite':
                        break;
                        
                    default:
                        this[name] = data[name];
                        break;
                }
            }
        };



        Class.registry = function (name) {

            registry_sprites[this.prototype.typeName = name] = this;
        };


    });



    /**
     * 玩家基类
     */
    var Player = Sprite.extend(function (Class) {



        this.__text_ = '1';



        /**
         * 形状 1,2,3,4,5,6
         */
        defineProperty(this, 'shape', {

            get: function () {

                return this.__shape || this.__shape_ || 1;
            },

            set: function (value) {

                this.__shape = value | 0;
            }
        });



        this.render = function (context, x, y) {

            context.save();

            context.strokeStyle = this.borderColor;

            if (!(context.fillStyle = this.backgroundColor))
            {
                this.renderStyle(context, x, y);
            }

            context.textAlign = 'center';
            context.textBaseline = 'middle';
            context.font = '24px Arial';

            if ((context.lineWidth = this.borderWidth) & 1)
            {
                context.translate(0.5, 0.5);
            }

            context.beginPath();

            switch (this.shape)
            {
                case 1:
                    context.arc(x, y, 18, 0, Math.PI * 2);
                    context.fill();
        
                    context.fillStyle = this.color;
                    context.fillText(this.text, x, y);
        
                    context.stroke();
                    break;

                case 2:
                    context.moveTo(x, y - 18);
                    context.lineTo(x - 18, y + 18);
                    context.lineTo(x + 18, y + 18);
                    context.closePath();
        
                    context.fill();
        
                    context.fillStyle = this.color;
                    context.fillText(this.text, x, y + 5);

                    context.stroke();
                    break;

                case 3:
                    context.strokeStyle = context.fillStyle;
                    
                    context.moveTo(x - 14, y - 14);
                    context.lineTo(x + 7, y + 7);

                    context.moveTo(x + 14, y - 14);
                    context.lineTo(x - 14, y + 14);

                    context.stroke();

                    context.fillText(this.text, x + 13, y + 13);
                    break;
                
                case 5:
                    context.rect(x - 18, y - 18, 36, 36);
                    context.fill();
        
                    context.fillStyle = this.color;
                    context.fillText(this.text, x, y);
        
                    context.stroke();
                    break;

                case 6:
                    context.arc(x, y, 12, 0, Math.PI * 2);
                    context.fill();

                    context.strokeStyle = context.fillStyle;
                    context.beginPath();

                    context.arc(x, y - 16, 20, Math.PI / 8, 7 * Math.PI / 8);
                    
                    context.lineWidth = 6;
                    context.stroke();

                    context.fillText(this.text, x + 16, y + 16);
                    break;

                default:
                    this.__render_shape(context, x, y);
                    break;
            }

            context.restore();
        };


        this.renderStyle = function (context, x, y) {

        };


        this.renderSelected = function (context, x, y) {

            var x1, y1, x2, y2;

            switch (this.shape)
            {
                case 1:
                case 2:
                case 3:
                case 5:
                    x1 = x - 18;
                    y1 = y - 18;
                    x2 = x + 18;
                    y2 = y + 18;
                    break;

                case 4:
                    x1 = x - 21;
                    y1 = y - 25;
                    x2 = x + 21;
                    y2 = y + 25;
                    break;

                case 6:
                    x1 = x - 20;
                    y1 = y - 12;
                    x2 = x + 20;
                    y2 = y + 25;
                    break;
            }

            context.fillStyle = 'red';

            context.fillRect(x1 - 3, y1 - 3, 6, 6);
            context.fillRect(x2 - 3, y1 - 3, 6, 6);
            context.fillRect(x1 - 3, y2 - 3, 6, 6);
            context.fillRect(x2 - 3, y2 - 3, 6, 6);
        };



        this.hitTest = function (x, y, left, top) {

            switch (this.shape)
            {
                case 1:
                    return hitTest.cycle(x, y, left, top, 18);

                case 2:
                    return hitTest.polygon(x, y, [left, top - 18, left - 18, top + 18, left + 18, top + 18]);
                
                case 4:
                    return hitTest.rect(x, y, left - 18, top - 22, 36, 44);

                default:
                    return hitTest.rect(x, y, left - 18, top - 18, 36, 36);
            }
        };


        this.serialize = function (array) {

            var any;

            Class.base.serialize.call(this, array);

            if (any = this.__shape)
            {
                array.push(',"shape":"', any, '"');
            }
        };


    });



    /**
     * 进攻玩家类
     */
    Player.extend(function (Class) {


        this.__shape_ = 1;
        

        this.__text_ = '1';



        this.renderStyle = function (context, x, y) {

            var style = context.createLinearGradient(x - 18, y - 22, x + 18, y + 22);

            style.addColorStop(0.3, '#53CBFE');
            style.addColorStop(0.8, '#3AA8FC');

            context.fillStyle = style;
        };


        this.__render_shape = function (context, x, y) {
            
            context.moveTo(x - 14, y - 16);
            context.lineTo(x - 5, y - 22);
            context.lineTo(x + 5, y - 22);
            context.lineTo(x + 14, y - 16);
            context.lineTo(x + 14, y + 12);
            context.lineTo(x - 14, y + 12);
            context.closePath();

            context.fill();

            context.beginPath();

            context.moveTo(x - 17.5, y - 4);
            context.lineTo(x - 17.5, y + 22);

            context.moveTo(x + 17.5, y - 4);
            context.lineTo(x + 17.5, y + 22);

            context.lineWidth = 3;
            context.strokeStyle = context.fillStyle;
            context.stroke();
        };


    }).registry('offense');



    /**
     * 防守玩家类
     */
    Player.extend(function (Class) {


        this.__shape_ = 2;

        
        this.__text_ = '1';


        this.renderStyle = function (context, x, y) {

            var style = context.createLinearGradient(x - 18, y - 22, x + 18, y + 22);

            style.addColorStop(0.3, '#70E085');
            style.addColorStop(0.8, '#3BC759');

            context.fillStyle = style;
        };
        

        this.__render_shape = function (context, x, y) {
            
            context.moveTo(x - 14, y - 22);
            context.lineTo(x + 14, y - 22);
            context.lineTo(x + 14, y + 12);
            context.lineTo(x - 14, y + 12);
            context.closePath();

            context.fill();

            context.beginPath();

            context.moveTo(x - 17.5, y - 4);
            context.lineTo(x - 17.5, y + 22);

            context.moveTo(x + 17.5, y - 4);
            context.lineTo(x + 17.5, y + 22);

            context.lineWidth = 3;
            context.strokeStyle = context.fillStyle;
            context.stroke();
        };


    }).registry('defense');



    /**
     * 球类
     */
    Sprite.extend(function (Class) {

        
        this.render = function (context, x, y) {

            context.beginPath();
            context.arc(x, y, 14, 0, Math.PI * 2);

            if (!(context.fillStyle = this.backgroundColor))
            {
                var style = context.createLinearGradient(x - 15, y - 15, x + 15, y + 15);

                style.addColorStop(0.3, '#F8A845');
                style.addColorStop(0.8, '#FF8E01');

                context.fillStyle = style;
            }

            context.fill();

            context.strokeStyle = this.borderColor;
            context.lineWidth = this.borderWidth;
            context.stroke();
        };


        this.renderSelected = function (context, x, y) {

            context.fillStyle = 'red';

            context.fillRect(x - 14 - 3, y - 14 - 3, 6, 6);
            context.fillRect(x + 14 - 3, y - 14 - 3, 6, 6);
            context.fillRect(x - 14 - 3, y + 14 - 3, 6, 6);
            context.fillRect(x + 14 - 3, y + 14 - 3, 6, 6);
        };



        this.hitTest = function (x, y, left, top) {

            return hitTest.cycle(x, y, left, top, 10);
        };


    }).registry('ball');



    /**
     * 障碍物类
     */
    Sprite.extend(function (Class) {


        this.render = function (context, x, y) {

            context.beginPath();

            context.moveTo(x, y - 19);
            context.lineTo(x - 12, y + 15);
            context.lineTo(x - 16, y + 15);
            context.lineTo(x - 16, y + 19);
            context.lineTo(x + 16, y + 19);
            context.lineTo(x + 16, y + 15);
            context.lineTo(x + 12, y + 15);
            context.closePath();

            if (!(context.fillStyle = this.backgroundColor))
            {
                var style = context.createLinearGradient(x - 16, y - 19, x + 16, y + 19);

                style.addColorStop(0.3, '#F8A845');
                style.addColorStop(0.8, '#FF8E01');

                context.fillStyle = style;
            }

            context.fill();

            context.strokeStyle = this.borderColor;
            context.lineWidth = this.borderWidth;
            context.stroke();
        };


        this.renderSelected = function (context, x, y) {

            context.fillStyle = 'red';

            context.fillRect(x - 20, y - 20, 6, 6);
            context.fillRect(x + 15, y - 20, 6, 6);
            context.fillRect(x - 20, y + 20, 6, 6);
            context.fillRect(x + 15, y + 20, 6, 6);
        };



        this.hitTest = function (x, y, left, top) {

            return hitTest.polygon(x, y, [
                left, top - 19,
                left - 12, top + 15,
                left - 16, top + 15,
                left - 16, top + 19,
                left + 16, top + 19,
                left + 16, top + 15,
                left + 12, top + 15
            ]);
        };


    }).registry('obstacle');



    /**
     * 线条类
     */
    var Line = Object.extend(function (Class) {



        // 线型绘制集合
        var shapes = Object.create(null);



        Class.ctor = function () {

            this.points = [];
        };



        // 类型名称
        this.typeName = 'line';



        /**
         * 线条形状 l1,l2,l3,l4,l5 c1,c2,c3,c4,c5
         */
        defineProperty(this, 'shape', {

            get: function () {

                return this.__shape || 'l1';
            },

            set: function (value) {

                this.__shape = '' + value;
            }
        });


        /**
         * 是否显示箭头
         */
        defineProperty(this, 'arrow', {

            get: function () {

                return this.__arrow || false;
            },

            set: function (value) {

                this.__arrow = !!value;
            }
        });


        /**
         * 线条粗细
         */
        defineProperty(this, 'thick', {

            get: function () {

                return this.__thick || 2;
            },

            set: function (value) {

                this.__thick = value | 0;
            }
        });


        /**
         * 线条颜色
         */
        defineProperty(this, 'color', {

            get: function () {

                return this.__color || 'black';
            },

            set: function (value) {

                this.__color = '' + value;
            }
        });



        this.render = function (context) {

            var fn = shapes[this.shape];

            if (fn)
            {
                context.lineWidth = this.thick;
                context.strokeStyle = this.color;
    
                fn(context, this.points);
            }
        };


        this.renderSelected = function (context, x, y) {

            var points = this.points,
                index = 0,
                length = points.length;

            context.fillStyle = 'red';

            while (index < length)
            {
                var x = points[index++],
                    y = points[index++];

                context.fillRect(x - 5, y - 5, 10, 10);
            }
        };



        // 画虚直线
        function dash_line(context, points) {

            var x1 = points[0],
                y1 = points[1],
                index = 2,
                length = points.length;

            context.beginPath();
            context.moveTo(x1, y1);

            while (index < length)
            {
                var x2 = points[index++],
                    y2 = points[index++],
                    dx = x2 - x1,
                    dy = y2 - y1,
                    distance = Math.sqrt(dx * dx + dy * dy),
                    angle = Math.atan(dy / dx),
                    dx1 = Math.cos(angle),
                    dy1 = Math.sin(angle),
                    dx2 = dx1 * 5,
                    dy2 = dy1 * 5;

                dx1 *= 8;
                dy1 *= 8;

                while (distance > 0)
                {
                    if ((distance -= 8) <= 0)
                    {
                        context.lineTo(x2, y2);
                        break;
                    }

                    context.lineTo(x1 += dx1, y1 += dy1);

                    if ((distance -= 5) <= 0)
                    {
                        break;
                    }

                    context.moveTo(x1 += dx2, y1 += dy2);
                }
            }

            context.stroke();
        };

        
        // 获取坐标点的等距偏移坐标
        function points_offset(points, offset) {

            var atan = Math.atan,
                sin = Math.sin,
                cos = Math.cos,
                length = (points.length >> 1) << 1,
                list = new Array(length),
                index = 0,
                i = 2,
                x1 = points[0],
                y1 = points[1];
            
            while (i < length)
            {
                var x2 = points[i++],
                    y2 = points[i++],
                    angle = atan((y2 - y1) / (x2 - x1)),
                    dx = offset * sin(angle),
                    dy = offset * cos(angle);
                
                if (x2 >= x1 && y2 < y1 || x1 <= x2 && y2 > y1)
                {
                    dx = -dx;
                }

                if (x2 < x1)
                {
                    dy = -dy;
                }

                if (i > 4)
                {
                    cross_point(
                        list[index - 4], list[index - 3], list[index - 2], list[index - 1],
                        x1 + dx, y1 + dy, x2 + dx, y2 + dy, 
                        list, index - 2);
                }
                else
                {
                    list[index++] = x1 + dx;
                    list[index++] = y1 + dy;
                }

                list[index++] = x2 + dx;
                list[index++] = y2 + dy;

                x1 = x2;
                y1 = y2;
            }

            return list;
        };


        // 获取线段的交点坐标
        function cross_point(ax, ay, bx, by, cx, cy, dx, dy, list, index) {

            // 三角形abc面积的2倍
            var abc = (ax - cx) * (by - cy) - (ay - cy) * (bx - cx);

            // 三角形abd面积的2倍
            var abd = (ax - dx) * (by - dy) - (ay - dy) * (bx - dx);

            // 三角形cda面积的2倍
            var cda = (cx - ax) * (dy - ay) - (cy - ay) * (dx - ax);

            // 三角形cdb面积的2倍
            // 不需要再用公式计算面积, 而是通过已知的三个面积加减得出.
            var cdb = cda + abc - abd;

            //计算交点坐标  
            var t = cda / (abd - abc);

            ax += t * (bx - ax);
            ay += t * (by - ay);

            if (list)
            {
                list[index] = ax;
                list[index + 1] = ay;
            }

            return [ax, ay];
        };


        // 画半圆直线
        function cycle_line(context, x1, y1, x2, y2) {

            var dx = x2 - x1,
                dy = y2 - y1,
                index = 0,
                length = (Math.sqrt(dx * dx + dy * dy) - 16 | 0) >> 3,
                PI = Math.PI / 2,
                sin = Math.sin,
                cos = Math.cos,
                angle,
                dx1,
                dy1,
                dx2,
                dy2;

            context.beginPath();
            context.moveTo(x1, y1);

            if (length > 0)
            {
                angle = Math.atan(dy / dx);

                dx1 = cos(angle) * 8;
                dy1 = sin(angle) * 8;

                dx2 = cos(PI + angle) * 6;
                dy2 = sin(PI + angle) * 6;

                // 调整角度
                if (dx < 0 && dx1 > 0 || dx > 0 && dx1 < 0)
                {
                    dx1 = -dx1;
                    dx2 = -dx2;
                }

                if (dy > 0 && dy1 < 0 || dy < 0 && dy1 > 0)
                {
                    dy1 = -dy1;
                    dy2 = -dy2;
                }

                context.lineTo(x1 += dx1, y1 += dy1);

                while (index++ < length)
                {
                    // 绘制上半圆
                    context.bezierCurveTo(x1 - dx2, y1 - dy2, (x1 += dx1) - dx2, (y1 += dy1) - dy2, x1, y1);

                    if (index++ < length)
                    {
                        // 绘制下半圆
                        context.bezierCurveTo(x1 + dx2, y1 + dy2, (x1 += dx1) + dx2, (y1 += dy1) + dy2, x1, y1);
                    }
                }
            }
            
            context.lineTo(x2, y2);
            context.stroke();
        };


        // 结束箭头
        function arrow_end(context, points) {

            var index = points.length - 1,
                x1 = points[index - 3],
                y1 = points[index - 2],
                x2 = points[index - 1],
                y2 = points[index],
                dx = x2 - x1,
                dy = y2 - y1,
                sin = Math.sin,
                cos = Math.cos,
                angle;

            context.fillStyle = context.strokeStyle;
            context.beginPath();

            if (Math.sqrt(dx * dx + dy * dy) < 2)
            {
                x1 = points[index - 5];
                y1 = points[index - 4];

                dx = x2 - x1;
                dy = y2 - y1;
            }

            angle = Math.atan(dy / dx);

            dx = cos(angle);
            dy = sin(angle);

            if (x2 < x1)
            {
                dx = -dx;
                dy = -dy;
            }

            context.moveTo(x2 + dx * 12, y2 + dy * 12);

            x1 = x2 - dx * 5;
            y1 = y2 - dy * 5;

            angle += Math.PI / 2;

            dx = cos(angle) * 8;
            dy = sin(angle) * 8;

            if (x2 < x1)
            {
                dx = -dx;
                dy = -dy;
            }

            context.lineTo(x1 + dx, y1 + dy);
            context.lineTo(x2, y2);
            context.lineTo(x1 - dx, y1 - dy);
            context.closePath();

            context.fill();
        };


        // 结束线段
        function line_end(context, points) {

            var index = points.length - 4,
                x1 = points[index++],
                y1 = points[index++],
                x2 = points[index++],
                y2 = points[index],
                dx = x2 - x1,
                dy = y2 - y1,
                angle = Math.atan(dy / dx);

            angle += Math.PI / 2;

            dx = Math.cos(angle) * 12;
            dy = Math.sin(angle) * 12;

            context.beginPath();

            context.moveTo(x2 - dx, y2 - dy);

            context.lineTo(x2 + dx, y2 + dy);

            context.stroke();
        };


        // 加虚曲线
        function dash_curve(context, points) {

            var sqrt = Math.sqrt,
                size = 8,
                distance = 0,
                index = 2,
                x1,
                y1,
                x2,
                y2,
                length;
    
            points = bezier.split(points, 2);

            length = points.length - 2;

            context.beginPath();

            context.moveTo(x1 = points[0], y1 = points[1]);

            while (index < length)
            {
                x2 = points[index++];
                y2 = points[index++];

                x1 = x2 - x1;
                y1 = y2 - y1;

                distance += sqrt(x1 * x1 + y1 * y1);
                
                if (distance >= size)
                {
                    if (size === 8)
                    {
                        size = 3;
                        context.lineTo(x2, y2);
                    }
                    else
                    {
                        size = 8;
                        context.moveTo(x2, y2);
                    }

                    distance = 0;
                }

                x1 = x2;
                y1 = y2;
            }

            context.lineTo(points[index++], points[index]);

            context.stroke();
        };


        // 画半圆曲线
        function cycle_curve(context, points) {

            var PI = Math.PI / 2,
                sqrt = Math.sqrt,
                atan = Math.atan,
                sin = Math.sin,
                cos = Math.cos,
                index = 4,
                length,
                angle,
                x1,
                y1,
                x2,
                y2,
                dx,
                dy,
                dx1,
                dy1,
                dx2,
                dy2,
                reverse;

            points = bezier.split(points, 6);

            length = points.length - 4;

            context.beginPath();

            context.moveTo(points[0], points[1]);
            context.lineTo(x1 = points[2], y1 = points[3]);

            while (index < length)
            {
                x2 = points[index++];
                y2 = points[index++];

                dx = x2 - x1;
                dy = y2 - y1;

                // 距离太近则跳过
                if (sqrt(dx * dx + dy * dy) < 3)
                {
                    continue;
                }

                angle = atan(dy / dx);

                dx1 = cos(angle) * 8;
                dy1 = sin(angle) * 8;

                dx2 = cos(PI + angle) * 6;
                dy2 = sin(PI + angle) * 6;

                // 调整角度
                if (dx < 0 && dx1 > 0 || dx > 0 && dx1 < 0)
                {
                    dx1 = -dx1;
                    dx2 = -dx2;
                }

                if (dy > 0 && dy1 < 0 || dy < 0 && dy1 > 0)
                {
                    dy1 = -dy1;
                    dy2 = -dy2;
                }

                // 绘制上半圆
                if (reverse = !reverse)
                {
                    context.bezierCurveTo(x1 - dx2, y1 - dy2, (x1 += dx1) - dx2, (y1 += dy1) - dy2, x1, y1);
                }
                else // 绘制下半圆
                {
                    context.bezierCurveTo(x1 + dx2, y1 + dy2, (x1 += dx1) + dx2, (y1 += dy1) + dy2, x1, y1);
                }
            }

            index = points.length - 2;
            context.lineTo(points[index++], points[index]);

            context.stroke();
        };
        


        shapes.l1 = function (context, points, arrow) {

            var index = 2,
                length = points.length;

            context.beginPath();
            context.moveTo(points[0], points[1]);

            while (index < length)
            {
                context.lineTo(points[index++], points[index++]);
            }
            
            context.stroke();

            if (arrow !== false)
            {
                arrow_end(context, points);
            }
        };

        
        shapes.l2 = function (context, points, arrow) {

            if (context.setLineDash)
            {
                context.save();
                context.setLineDash([8, 5]);

                shapes.l1(context, points, arrow);

                context.restore();
            }
            else // 不支持虚线时特殊处理
            {
                dash_line(context, points);

                if (arrow !== false)
                {
                    arrow_end(context, points);
                }
            }
        };


        shapes.l3 = function (context, points, arrow) {

            var index = 2,
                length = points.length,
                x1 = points[0],
                y1 = points[1],
                x2,
                y2;

            context.beginPath();
            context.moveTo(x1, y1);

            while (index < length)
            {
                x2 = points[index++];
                y2 = points[index++];

                cycle_line(context, x1, y1, x1 = x2, y1 = y2);
            }

            context.stroke();

            if (arrow !== false)
            {
                arrow_end(context, points);
            }
        };


        shapes.l4 = function (context, points, arrow) {

            shapes.l1(context, points, false);

            if (arrow !== false)
            {
                line_end(context, points);
            }
        };


        shapes.l5 = function (context, points, arrow) {

            shapes.l1(context, points_offset(points, -2), false);
            shapes.l1(context, points_offset(points, 2), false);

            if (arrow !== false)
            {
                arrow_end(context, points);
            }
        };

       

        shapes.c1 = function (context, points, arrow) {

            if (points.length < 5)
            {
                shapes.l1(context, points, arrow);
            }
            else
            {
                var index = 2,
                    length = (points = bezier.controls(points)).length;

                context.beginPath();
                context.moveTo(points[0], points[1]);

                while (index < length)
                {
                    context.bezierCurveTo(points[index++], points[index++], points[index++], points[index++], points[index++], points[index++]);
                }
                
                context.stroke();

                if (arrow !== false)
                {
                    arrow_end(context, points);
                }
            }
        };

        
        shapes.c2 = function (context, points, arrow) {

            if (points.length < 5)
            {
                shapes.l2(context, points, arrow);
            }
            else if (context.setLineDash)
            {
                context.save();
                context.setLineDash([8, 5]);

                shapes.c1(context, points);

                context.restore();
            }
            else // 不支持虚线时特殊处理
            {
                dash_curve(context, points = bezier.controls(points));

                if (arrow !== false)
                {
                    arrow_end(context, points);
                }
            }
        };


        shapes.c3 = function (context, points, arrow) {

            if (points.length < 5)
            {
                shapes.l3(context, points, arrow);
            }
            else
            {
                cycle_curve(context, points = bezier.controls(points));

                if (arrow !== false)
                {
                    arrow_end(context, points);
                }
            }
        };


        shapes.c4 = function (context, points, arrow) {

            if (points.length < 5)
            {
                shapes.l4(context, points, arrow);
            }
            else
            {
                shapes.c1(context, points, false);

                if (arrow !== false)
                {
                    line_end(context, points);
                }
            }
        };


        shapes.c5 = function (context, points, arrow) {

            if (points.length < 5)
            {
                shapes.l5(context, points, arrow);
            }
            else
            {            
                shapes.c1(context, points_offset(points, -2), false);
                shapes.c1(context, points_offset(points, 2), false);

                if (arrow !== false)
                {
                    arrow_end(context, points);
                }
            }
        };



        this.hitTest = function (x, y) {

            var points = this.points;

            if (this.shape[0] === 'l')
            {
                return hitTest.line(x, y, points, 6);
            }

            points = points.controls || bezier.controls(points);

            return hitTest.bezier(x, y, points, 6);
        };


        // 获取指定坐标的控制点索引
        this.pointAt = function (x, y) {

            var points = this.points,
                index = 0,
                length = points.length,
                x1,
                y1;

            while (index < length)
            {
                x1 = points[index++];
                y1 = points[index++];

                if (x >= x1 - 5 && x <= x1 + 5 && y >= y1 - 5 && y <= y1 + 5)
                {
                    return index - 2;
                }
            }

            return -1;
        };



        this.location = function (frame) {

            return this.points.slice(0);
        };


        this.dragMove = function (start, distanceX, distanceY, frame) {

            var points = this.points,
                index = 0,
                length = points.length;

            while (index < length)
            {
                points[index] = start[index++] + distanceX;
                points[index] = start[index++] + distanceY;
            }

            return points;
        };



        this.serialize = function (array) {

            var any;

            array.push('"shape":"', this.shape, '"');

            if (any = this.__arrow)
            {
                array.push(',"arrow":1');
            }

            if (any = this.__thick)
            {
                array.push(',"thick":1');
            }

            if (any = this.__color)
            {
                array.push(',"color":"', any, '"');
            }

            array.push(',"points":[', this.points.join(','), ']');
        };


        this.deserialize = function (data) {

            var any;

            if (any = data.shape)
            {
                this.shape = any;
            }

            if (data.arrow)
            {
                this.arrow = 1;
            }

            if (any = data.thick)
            {
                this.thick = any;
            }

            if (any = data.color)
            {
                this.color = any;
            }

            if (any = data.points)
            {
                this.points.push.apply(this.points, any);
            }
        };



    });



    /**
     * 关键帧类
     */
    var Frame = Object.extend(function (Class) {



        var indexOf = Array.prototype.indexOf;

        

        Class.ctor = function (stage) {

            this.stage = stage;
            this.lines = [];
            this.points = {};
        };


        /**
         * 帧索引
         */
        defineProperty(this, 'index', {

            get: function () {

                return indexOf.call(this.stage.frames, this);
            }
        });



        /**
         * 是否暂停
         */
        defineProperty(this, 'pause', {

            get: function () {

                return this.__pause || false;
            },

            set: function (value) {

                this.__pause = !!value;
            }
        });


        /**
         * 持续时间
         */
        defineProperty(this, 'duration', {

            get: function () {

                return this.__duration || 1;
            },

            set: function (value) {

                this.__duration = value | 0;
            }
        });


        /**
         * 参与人数
         */
        defineProperty(this, 'playerNumber', {

            get: function () {

                return this.__players || 0;
            },

            set: function (value) {

                this.__players = value | 0;
            }
        });


        /**
         * 球数量
         */
        defineProperty(this, 'ballNumber', {

            get: function () {

                return this.__balls || 0;
            },

            set: function (value) {

                this.__balls = value | 0;
            }
        });


        /**
         * 篮筐数
         */
        defineProperty(this, 'basketNumber', {

            get: function () {

                return this.__baskets || 0;
            },

            set: function (value) {

                this.__baskets = value | 0;
            }
        });


        /**
         * 器械数
         */
        defineProperty(this, 'equipmentNumber', {

            get: function () {

                return this.__equipments || 0;
            },

            set: function (value) {

                this.__equipments = value | 0;
            }
        });


        /**
         * 是否分组，
         */
        defineProperty(this, 'isGroup', {

            get: function () {

                return this.__group || false;
            },

            set: function (value) {

                this.__group = !!value;
            }
        });


        /**
         * 描述
         */
        defineProperty(this, 'describe', {

            get: function () {

                return this.__describe || '';
            },

            set: function (value) {

                this.__describe = '' + value;
            }
        });


        /**
         * 其它
         */
        defineProperty(this, 'other', {

            get: function () {

                return this.__other || '';
            },

            set: function (value) {

                this.__other = '' + value;
            }
        });

        
        /**
         * 上一帧
         */
        defineProperty(this, 'previous', {

            get: function () {

                var frames = this.stage.frames;
                return frames[indexOf.call(frames, this) - 1] || null;
            }
        });


        /**
         * 下一帧
         */
        defineProperty(this, 'next', {

            get: function () {

                var frames = this.stage.frames;
                return frames[indexOf.call(frames, this) + 1] || null;
            }
        });



        this.render = function (context, type, except) {

            var points = this.points,
                list = this.stage.sprites,
                index = 0,
                item,
                point;

            if (type & 1)
            {
                while (item = list[index++])
                {
                    if (item !== except && item.visible && (point = points[item.id]))
                    {
                        item.render(context, point[0], point[1]);
                    }
                }
            }

            if (type & 2)
            {
                list = this.lines;
                index = 0;
    
                while (item = list[index++])
                {
                    if (item !== except)
                    {
                        item.render(context);
                    }
                }
            }
        };

        
        this.animate = function (context, nextPoints, offset) {

            var points = this.points,
                list = this.stage.sprites,
                index = 0,
                item,
                point,
                any;

            while (item = list[index++])
            {
                if (point = points[any = item.id])
                {
                    if (any = nextPoints[any])
                    {
                        item.render(context, point[0] + (any[0] - point[0]) * offset, point[1] + (any[1] - point[1]) * offset);
                    }
                    else
                    {
                        item.render(context, point[0], point[1]);
                    }
                }
            }
        };



        this.serialize = function (array, keys) {

            var value = this.points,
                flag;

            array.push('"points":{');

            for (var name in value)
            {
                // 移除不存在的元素的坐标
                if (keys[name])
                {
                    if (flag)
                    {
                        array.push(',');
                    }
                    else
                    {
                        flag = 1;
                    }
    
                    array.push('"', name, '":[', value[name].join(','), ']');
                }
            }

            array.push('},"lines":[');

            value = this.lines;

            for (var i = 0, l = value.length; i < l; i++)
            {
                array.push(i > 0 ? ',{' : '{');

                value[i].serialize(array);

                array.push('}');
            }

            array.push(']');

            if (this.__pause)
            {
                array.push(',"pause":1');
            }

            if (this.__group)
            {
                array.push(',"isGroup":1');
            }

            if (value = this.__duration)
            {
                array.push(',"duration":', value);
            }

            if (value = this.__players)
            {
                array.push(',"playerNumber":', value);
            }

            if (value = this.__balls)
            {
                array.push(',"ballNumber":', value);
            }

            if (value = this.__baskets)
            {
                array.push(',"basketNumber":', value);
            }

            if (value = this.__equipments)
            {
                array.push(',"equipmentNumber":', value);
            }

            if (value = this.__describe)
            {
                array.push(',"describe":"', value.replace(/"/g, '\\"'), '"');
            }

            if (value = this.__other)
            {
                array.push(',"other":"', value.replace(/"/g, '\\"'), '"');
            }
        };


        this.deserialize = function (data) {

            for (var name in data)
            {
                var value = data[name];

                switch (name)
                {
                    case 'points':
                        this.points = value;
                        break;

                    case 'lines':
                        if (value && value[0])
                        {
                            var lines = this.lines;
            
                            for (var i = 0, l = value.length; i < l; i++)
                            {
                                var line = new Line();
            
                                line.deserialize(value[i]);
                                lines.push(line);
                            }
                        }
                        break;

                    default:
                        this[name] = value;
                        break;
                }
            }
        };



        this.dispose = function () {

            this.points.length = this.lines.length = 0;
            this.stage = this.sprites = null;
        };


    });




    /**
     * 舞台类
     */
    var Stage = Object.extend(function (Class) {
    
    

        // 鼠标是否按下
        var mousedown = {};


        // 正在添加的线条
        var addingLine = null;


        // 数组项删除或插入
        var splice = Array.prototype.splice;



                
        Class.ctor = function (host) {
    
            var dom = this.dom = document.createElement('div'),
                canvas = this.canvas = document.createElement('canvas'),
                context = this.context = canvas.getContext('2d'),
                width = canvas.width = this.canvasWidth,
                height = canvas.height = this.canvasHeight,
                style1 = dom.style,
                style2 = canvas.style;

            style1.width = style2.width = width + 'px';
            style1.height = style2.height = height + 'px';

            style1.position = 'relative';
            style2.position = 'absolute'
            style2.left = style2.top = '50%';
            style2.marginLeft = -(width >> 1) + 'px';
            style2.marginTop = -(height >> 1) + 'px';

            dom.appendChild(canvas);

            if (host)
            {
                host.appendChild(dom);
            }
            
            canvas.ondragstart = canvas.onselectstart = function () {

                return false;
            };

            this.sprites = [];
            this.frames = { length: 1, 0: new Frame(this) };
        };



        /**
         * 球场类型 full:全场 half-up:上半场 half-down:下半场
         */
        defineProperty(this, 'type', {

            get: function () {

                return this.__type || 'full';
            },

            set: function (value) {

                this.__type = value;
                this.__background = null;

                reset_size(this, this.scale);
            }
        });


        /**
         * 控件宽度
         */
        defineProperty(this, 'width', {

            get: function () {

                return this.dom.style.width;
            },

            set: function (value) {

                this.__background = null;
                this.dom.style.width = value > 0 ? value + 'px' : value;
            }
        });


        /**
         * 控件高度
         */
        defineProperty(this, 'height', {

            get: function () {

                return this.dom.style.height;
            },

            set: function (value) {

                this.__background = null;
                this.dom.style.height = value > 0 ? value + 'px' : value;
             }
        });


        /**
         * 缩放比例
         */
        defineProperty(this, 'scale', {

            get: function () {

                return this.__scale || 1;
            },

            set: function (value) {

                value = +value || 1;

                if (value <= 0)
                {
                    value = 1;
                }

                if (this.__scale !== value)
                {
                    reset_size(this, this.__scale = value);
                }
            }
        });


        function reset_size(stage, scale) {

            var canvas = stage.canvas,
                style = canvas.style,
                width = stage.canvasWidth,
                height = stage.type === 'full' ? stage.canvasHeight : stage.canvasHalfHeight;

            canvas.width = width;
            canvas.height = height;

            width = width * scale | 0;
            height = height * scale | 0;

            style.width = width + 'px';
            style.height = height + 'px';
            style.marginLeft = -(width >> 1) + 'px';
            style.marginTop = -(height >> 1) + 'px';
        };



        /**
         * 是否设计模式
         */
        defineProperty(this, 'designMode', {

            get: function () {

                return this.__designMode || false;
            },

            set: function (value) {

                design(this, this.__designMode = !!value);
            }
        });


        /**
         * 是否支持历史记录(undo, redo)
         */
        defineProperty(this, 'history', {

            get: function () {

                return this.__history || false;
            },

            set: function (value) {

                var stack = this.historyStack;

                if (this.__history = !!value)
                {
                    if (!stack)
                    {
                        (this.historyStack = []).index = 0;
                    }
                }
                else
                {
                    this.historyStack = null;
                }
            }
        });



        /**
         * 是否正在播放
         */
        defineProperty(this, 'isPlaying', {

            get: function () {

                return this.__animate > 0;
            }
        });



        this.__index = 0;

        /**
         * 当前帧索引
         */
        defineProperty(this, 'index', {
          
            get: function () {

                return this.__index;
            },
            set: function (value) {

                value |= 0;

                if (value < 0)
                {
                    value = 0;
                }
                else if (value >= this.frames.length)
                {
                    value = this.frames.length - 1;
                }

                this.__index = value;
                this.selectedItem = null;

                if ((value = this.historyStack) && value.length > 0)
                {
                    value.length = value.index = 0;
                    raiseHistoryChange(this);
                }

                if (value = this.onframechange)
                {
                    value.call(this);
                }
            }
        });


        /**
         * 当前帧
         */
        defineProperty(this, 'currentFrame', {

            get: function () {

                return this.frames[this.__index];
            }
        });


        /**
         * 当前选中项
         */
        defineProperty(this, 'selectedItem', {

            get: function () {

                return this.__selectedItem || null;
            },

            set: function (value) {

                value = value || null;

                if (this.__selectedItem !== value)
                {
                    raiseSelectedChange(this, value);
                }
            }
        });



        function design(stage, enabled) {

            var canvas = stage.canvas;

            canvas.stage = enabled ? stage : null;
            canvas.onmousedown = enabled ? onmousedown : null;
            canvas.onmousemove = enabled ? onmousemove : null;
            canvas.ondblclick = enabled ? ondblclick : null;

            document.removeEventListener('mouseup', onmouseup);

            if (enabled)
            {
                document.addEventListener('mouseup', onmouseup);
            }
        };


        function onmousedown(event) {

            var stage = this.stage, 
                frame = stage.currentFrame,
                point = event_point(stage, event),
                start = mousedown,
                item,
                points;

            // 点击生成线条
            if (item = addingLine)
            {
                points = item.points;

                if (start.move)
                {
                    start.move = 0;
                    points.splice(-2, 2);

                    if (points[points.length - 2] !== point.x || points[points.length - 1] !== point.y)
                    {
                        points.push(point.x, point.y);
                    }
                }
                else if (!points.length)
                {
                    stage.startBuffer(item);

                    if (start.enabled)
                    {
                        start.enabled = 0;
                        start.stage = start.frame = start.selected = null;
                    }
    
                    if (stage.selectedItem)
                    {
                        raiseSelectedChange(stage, null);
                    }
    
                    points.push(point.x, point.y);
                }
            }
            else // 检测是否调整线条
            {
                if ((item = stage.selectedItem) && item instanceof Line && (start.line = item.pointAt(point.x, point.y)) >= 0)
                {
                    start.selected = item;
                    start.points = item.points.slice(0);
                    start.location = [item.points[start.line], item.points[start.line + 1]];
                }
                else
                {
                    item = stage.hitTest(point.x, point.y);
    
                    if (start.selected = item)
                    {
                        raiseSelectedChange(stage, item);
                    }
                    else
                    {
                        if (stage.selectedItem)
                        {
                            raiseSelectedChange(stage, null);
                            stage.render();
                        }
    
                        return;
                    }
    
                    start.line = -1;
                    start.location = start.points = item.location(frame);
                }
               
                start.move = 0;
                start.enabled = 1;
                start.stage = stage;
                start.frame = frame;
                start.clientX = event.clientX;
                start.clientY = event.clientY;

                stage.startBuffer(item);
                stage.renderBuffer(item);
            }
        };


        function onmousemove(event) {

            var stage = this.stage,
                start = mousedown,
                item,
                point;

            if (item = addingLine)
            {
                if (item.points.length > 0)
                {
                    if (start.move)
                    {
                        item.points.splice(-2, 2);
                    }
                    else
                    {
                        start.move = 1;
                    }

                    point = event_point(stage, event);
                    item.points.push(point.x, point.y);
                    stage.renderBuffer(item);
                }
            }
            else if (start.enabled && (item = start.selected))
            {
                var scale = stage.scale,
                    distanceX = (event.clientX - start.clientX) / scale | 0,
                    distanceY = (event.clientY - start.clientY) / scale | 0;

                if (!start.move)
                {
                    if (distanceX < -2 || distanceX > 2 || distanceY < -2 || distanceY > 2)
                    {
                        start.move = 1;
                    }
                    else
                    {
                        return;
                    }
                }

                if (start.line < 0)
                {
                    item.dragMove(start.location, distanceX, distanceY, start.frame);
                }
                else
                {
                    item.points[start.line] = start.location[0] + distanceX;
                    item.points[start.line + 1] = start.location[1] + distanceY;
                    item.points.controls = null;
                }
                
                stage.renderBuffer(item);
            }
        };


        function onmouseup() {

            var start = mousedown;

            if (!addingLine && start.enabled)
            {
                var stage = start.stage,
                    item = start.selected;

                if (start.move)
                {
                    push_history(stage, [31, item, start.points, item.location(start.frame)]);
                }

                stage.renderBuffer(item);
                stage.closeBuffer();

                start.enabled = start.move = 0;
                start.stage = start.frame = start.selected = null;
            }
        };


        function ondblclick() {

            var line = addingLine;

            if (line)
            {
                var stage = this.stage;

                addingLine = null;

                if (mousedown.move)
                {
                    mousedown.move = 0;
                    line.points.splice(-2, 2);
                }

                stage.renderBuffer(line);
                stage.closeBuffer();

                push_history(stage, [12, line]);

                if (stage.onaddedline)
                {
                    stage.onaddedline(line);
                }

                return line;
            }
        };


        /**
         * 取消线条绘制
         */
        this.cancelLine = function () {

            var line = addingLine;

            if (line)
            {
                var any = this.currentFrame;

                addingLine = null;

                if (any && (any = any.lines) && any[any.length - 1] === line)
                {
                    any.pop();
                }

                mousedown.move = 0;
 
                this.closeBuffer();
                this.render();

                return line;
            }
        };



        /**
         * 在帧队列的最后添加帧
         */
        this.appendFrame = function () {

            var frames = this.frames,
                frame = new Frame(this),
                source = frames[frames.length].points,
                target = frame.points,
                value;

            for (var name in source)
            {
                if ((value = source[name]) && value.slice)
                {
                    target[name] = value.slice(0);
                }
            }

            return frames[frames.length++] = frame;
        };


        /**
         * 在当前帧后插入新的帧
         */
        this.insertFrame = function () {

            var frame = new Frame(this),
                source = this.currentFrame.points,
                target = frame.points,
                value;

            for (var name in source)
            {
                if ((value = source[name]) && value.slice)
                {
                    target[name] = value.slice(0);
                }
            }

            splice.call(this.frames, this.index + 1, 0, frame);

            return frame;
        };


        /**
         * 移除指定帧或当前帧
         * @param {*} frame  要移除的帧
         */
        this.removeFrame = function (frame) {

            var frames = this.frames,
                index = frame >= 0 ? frame : frame.index;

            if (index >= 0 && (frame = splice.call(frames, index, 1)[0]))
            {
                frame.dispose();

                if (this.__index === index)
                {
                    if (!frames[index] && !frames[--index])
                    {
                        frames.length = 1;
                        frames[index = 0] = new Frame(this);
                    }

                    this.index = index;
                }
            }
        };



        /**
         * 是否可以回退
         */
        this.canUndo = function () {

            var stack = this.historyStack;
            return stack ? stack.index > 0 : false;
        };


        /**
         * 是否可以重做
         */
        this.canRedo = function () {

            var stack = this.historyStack;
            return stack ? stack.index < stack.length : false;
        };


        /**
         * 回退
         */
        this.undo = function () {

            var stack = this.historyStack,
                frame = this.currentFrame,
                points = frame.points,
                command,
                item;
            
            if (stack && stack.index > 0)
            {
                command = stack[--stack.index];

                switch (command[0])
                {
                    case 11: // add sprite
                        this.sprites.pop();
                        break;

                    case 12: // add line
                        frame.lines.pop();
                        break;
                        
                    case 21: // remove sprite
                        item = command[2];
                        points[item.id] = command[3];
                        this.sprites.splice(command[1], 0, item);
                        break;

                    case 22: // remove line
                        item = command[2];
                        points[item.id] = command[3];
                        frame.lines.splice(command[1], 0, item);
                        break;

                    case 31: // move 
                        item = command[1];

                        if (item instanceof Line)
                        {
                            item.points = command[2];
                        }
                        else
                        {
                            points[item.id] = command[2];
                        }
                        break;
                }

                if (command[0] !== 31)
                {
                    this.selectedItem = null;
                }

                raiseHistoryChange(this);
                this.render();
            }
        };


        /**
         * 重做
         */
        this.redo = function () {

            var stack = this.historyStack,
                frame = this.currentFrame,
                points = frame.points,
                command,
                item;
            
            if (stack && stack.index < stack.length)
            {
                command = stack[stack.index++];

                switch (command[0])
                {
                    case 11: // add sprite
                        item = command[1];
                        points[item.id] = command[2];
                        this.sprites.push(item);
                        break;

                    case 12: // add line
                        frame.lines.push(command[1]);
                        break;
                        
                    case 21: // remove sprite
                        this.sprites.splice(command[1], 1);
                        break;

                    case 22: // remove line
                        frame.lines.splice(command[1], 0);
                        break;

                    case 31: // move 
                        item = command[1];

                        if (item instanceof Line)
                        {
                            item.points = command[3];
                        }
                        else
                        {
                            points[item.id] = command[3];
                        }
                        break;
                }

                if (command[0] !== 31)
                {
                    this.selectedItem = null;
                }

                raiseHistoryChange(this);
                this.render();
            }
        };


        function push_history(stage, command) {

            var stack = stage.historyStack;

            if (stack)
            {
                if (stack.index < stack.length - 1)
                {
                    stack.splice(stack.index);
                }

                stack.push(command);
                stack.index = stack.length;

                raiseHistoryChange(stage);
            }
        };


        function raiseHistoryChange(stage) {

            var fn = stage.onhistorychange;

            if (fn)
            {
                fn.call(stage);
            }
        };


    
        this.render = function (frame, context, type) {
    
            context = context || this.context;

            context.drawImage(this.__background || this.__render_background(), 0, 0);
  
            if (frame || (frame = this.currentFrame))
            {
                frame.render(context, type || 3);
            }

            if (this.selectedItem)
            {
                renderItem(context, frame, this.selectedItem, 2);
            }
        };


        function renderItem(context, frame, item, tag) {

            var points;

            if (item instanceof Line)
            {
                if (tag & 1)
                {
                    item.render(context);
                }
                
                if (tag & 2)
                {
                    item.renderSelected(context);
                }
            }
            else if (frame && (points = frame.points[item.id]))
            {
                if (tag & 1)
                {
                    item.render(context, points[0], points[1]);
                }
                
                if (tag & 2)
                {
                    item.renderSelected(context, points[0], points[1]);
                }
            }
        };


        /**
         * 开启缓存渲染
         * @param {*} item  排除的目标对象
         */
        this.startBuffer = function (item) {

            var frame = this.currentFrame;

            if (frame)
            {
                var canvas = this.canvas,
                    context = this.context;

                context.drawImage(this.__background || this.__render_background(), 0, 0);

                this.__buffer2 = null;

                if (item instanceof Line)
                {
                    frame.render(context, 3, item);
                    this.__buffer1 = context.getImageData(0, 0, canvas.width, canvas.height);
                }
                else
                {
                    frame.render(context, 1, item);
                    this.__buffer1 = context.getImageData(0, 0, canvas.width, canvas.height);

                    if (frame.lines.length > 0)
                    {
                        // 使用canvas缓存线条, toDataURL会卡顿, putImageData会冲掉背景
                        canvas = this.__buffer2 = canvas.cloneNode();
                        context = canvas.getContext('2d');
   
                        frame.render(context, 2, item);
                    }
                }

                return this.__buffer1;
            }
        };

        
        /**
         * 缓存渲染
         * @param {*} item  缓存渲染的目标 
         */
        this.renderBuffer = function (item) {

            var context = this.context,
                any;

            if (any = this.__buffer1 || this.startBuffer(item))
            {
                context.putImageData(any, 0, 0);
            }

            if (item)
            {
                renderItem(context, this.currentFrame, item, this.selectedItem === item ? 3 : 1);
            }

            if (any = this.__buffer2)
            {
                context.drawImage(this.__buffer2, 0, 0);
            }
        };


        /**
         * 关闭缓存渲染
         */
        this.closeBuffer = function () {

            this.__buffer1 = this.__buffer2 = null;
        };



        /**
         * 刷新画布
         */
        this.update = function () {

            this.render(this.currentFrame);
            raiseStateChange(this, this.index);
        };
    


        // 获取鼠标事件相关的坐标位置
        function event_point(stage, event) {

            var scale = stage.scale,
                rect = stage.canvas.getBoundingClientRect(),
                x = event.clientX - rect.left | 0,
                y = event.clientY - rect.top | 0;

            if (scale !== 1)
            {
                x = x / scale;
                y = y / scale;
            }

            return { x: x, y: y };
        };

        
    
        /**
         * 命中检测
         * @param {*} x  x坐标 
         * @param {*} y  y坐标
         */
        this.hitTest = function (x, y) {
    
            var frame = this.currentFrame;

            if (frame)
            {
                var sprites = this.sprites,
                    list = frame.points,
                    index = sprites.length - 1,
                    item,
                    any;
        
                while (item = sprites[index--])
                {
                    if ((any = list[item.id]) && item.hitTest(x, y, any[0], any[1]))
                    {
                        return item;
                    }
                }

                if ((list = frame.lines) && list[0])
                {
                    index = list.length - 1;

                    while (item = list[index--])
                    {
                        if (item.hitTest(x, y))
                        {
                            return item;
                        }
                    }
                }
            }
        };
    


        // 播放的帧偏移位置
        this.__offset = 0;


        // 播放动画
        function animate(stage, frame) {

            var next = frame.next;

            if (next)
            {
                var fn = (function () {

                    var context = this.context;
    
                    context.drawImage(this.__background || this.__render_background(), 0, 0);

                    frame.animate(context, next.points, this.__offset / 100);
    
                    if ((this.__offset += 2) <= 100)
                    {
                        this.__animate = setTimeout(fn, 20);
                    }
                    else
                    {
                        this.moveTo(this.index + 1, 1);

                        if (next.pause)
                        {
                            this.render(next);
                        }
                        else
                        {
                            animate(this, next);
                        }
                    }
    
                }).bind(stage);

                stage.__animate = setTimeout(fn, 20);
                raiseStateChange(stage, frame.index);
            }
            else
            {
                stage.__animate = stage.__offset = 0;
            }
        };

        
        /**
         * 播放
         */
        this.play = function (frame) {

            if ((frame || (frame = this.currentFrame)) && frame.next)
            {                    
                if (this.__animate)
                {
                    clearTimeout(this.__animate);
                    this.__animate = 0;
                }

                animate(this, frame);
                raiseStateChange(this, this.index);
            }
        };


        /**
         * 暂停
         */
        this.pause = function () {

            if (this.__animate)
            {
                clearTimeout(this.__animate);

                this.__animate = 0;
                raiseStateChange(this, this.index);
            }
        };



        function raiseSelectedChange(stage, item) {

            var fn = stage.onselectedchange;

            stage.__selectedItem = item;

            if (fn && typeof fn === 'function')
            {
                fn.call(stage, {
                    stage: stage,
                    selectedItem: item
                });
            }
        };


        function raiseStateChange(stage, index) {

            var fn = stage.onstatechange;
            
            if (fn && typeof fn === 'function')
            {
                var play = stage.__animate > 0,
                    next = index < stage.frames.length - 1;

                fn.call(stage, {

                    play: !play && next,
                    pause: play,
                    first: index > 0,
                    previous: index > 0,
                    next: next,
                    last: next
                });
            }
        };



        /**
         * 移动到第一帧
         */
        this.first = function () {

            return this.moveTo(0);
        };


        /**
         * 移动到上一帧
         */
        this.previous = function () {

            return this.moveTo(this.index - 1);
        };


        /**
         * 移动到下一帧
         */
        this.next = function () {

            return this.moveTo(this.index + 1);
        };


        /**
         * 移动到最后一帧
         */
        this.last = function () {

            return this.moveTo(this.frames.length - 1);
        };


        /**
         * 移到到指定索引的帧
         * @param {*} index 索引
         */
        this.moveTo = function (index, type) {

            var frames = this.frames,
                frame;

            if (this.__animate)
            {
                clearTimeout(this.__animate);
                this.__animate = 0;
            }

            this.__offset = 0;

            if (frame = frames[index])
            {
                this.render(frame, 0, type);

                raiseStateChange(this, this.index = index);

                return frame;
            }

            raiseStateChange(this, this.index = 0);

            return frames[0] || null;
        };



        /**
         * 添加元素
         * @param {*} type  元素类型
         * 
         * @param {*} properties  元素属性
         */
        this.addElement = function (type, x, y, properties) {

            var sprite = new (registry_sprites[type] || registry_sprites.offense)(),
                frames = this.frames;

            if (properties)
            {
                for (var name in properties)
                {
                    sprite[name] = properties[name];
                }
            }

            this.sprites.push(sprite);

            x |= 0;
            y |= 0;

            for (var i = this.currentFrame.index, l = frames.length; i < l; i++)
            {
                frames[i].points[sprite.id] = [x, y];
            }

            push_history(this, [11, sprite, [x, y]]);

            return sprite;
        };


        /**
         * 添加线条
         * @param {*} type  线条类型
         * @param {*} properties  线条属性 
         */
        this.addLine = function (shape, properties) {

            var line = new Line();

            line.shape = shape || 'l1';

            if (properties)
            {
                for (var name in properties)
                {
                    line[name] = properties[name];
                }
            }

            this.currentFrame.lines.push(line);
            return addingLine = line;
        };


        /**
         * 导出图片
         * @param {*} frame  要导出的帧
         * @param {*} scale  缩放比例
         */
        this.toDataURL = function (frame, scale) {

            if (frame || (frame = this.currentFrame))
            {
                var canvas = this.__canvasBuffer || (this.__canvasBuffer = this.canvas.cloneNode()),
                    context = canvas.getContext('2d');

                if (!scale || scale < 0)
                {
                    scale = 1;
                }

                canvas.width = this.canvas.width * scale;
                canvas.height = this.canvas.height * scale;

                context.save();
                context.scale(scale, scale);

                this.render(frame, context);

                context.restore();

                return canvas.toDataURL('image/png');
            }
        };
        
    

        function compute_keys(sprites) {

            var keys = Object.create(null);

            for (var i = sprites.length; i--;)
            {
                keys[sprites[i].id] = 1;
            }

            return keys;
        };


        /**
         * 序列化当前战术板为json字符串
         */
        this.serialize = function () {

            var array = ['{'],
                sprites = this.sprites,
                frames = this.frames,
                keys = compute_keys(sprites);

            if (this.type !== 'full')
            {
                array.push('"type":"', this.type, '",');
            }
            
            array.push('"sprites":[');
  
            for (var i = 0, l = sprites.length; i < l; i++)
            {
                array.push(i > 0 ? ',{' : '{');

                sprites[i].serialize(array);

                array.push('}');
            }

            array.push('],"frames":[');

            for (var i = 0, l = frames.length; i < l; i++)
            {
                array.push(i > 0 ? ',{' : '{');

                frames[i].serialize(array, keys);

                array.push('}');
            }

            array.push(']}');

            return array.join('');
        };

      
        /**
         * 从指定的json字符串或对象加载战术板
         * @param {*} json json字符串或对象
         */
        this.deserialize = function (json) {

            var registry = registry_sprites,
                frames = this.frames,
                sprites = this.sprites,
                list,
                item;

            this.clear();

            if (typeof json === 'string')
            {
                json = JSON.parse(json);
            }

            if (json.type)
            {
                this.type = json.type;
            }

            if (list = json.sprites)
            {
                for (var i = 0, l = list.length; i < l; i++)
                {
                    item = new (registry[list[i].sprite] || registry.offense)();
                    item.deserialize(list[i]);

                    sprites.push(item);
                }
            }

            if (list = json.frames)
            {
                frames[0].dispose();
                frames[frames.length = 0] = null;

                for (var i = 0, l = list.length; i < l; i++)
                {
                    item = new Frame(this);
                    item.deserialize(list[i]);

                    frames[frames.length++] = item;
                }
            }
        };



        function remove_spirte(stage, sprite) {

            var sprites = stage.sprites;

            for (var i = sprites.length; i--;)
            {
                if (sprites[i] === sprite)
                {
                    var id = sprite.id,
                        points = stage.currentFrame.points;

                    sprites.splice(i, 1);
                    push_history(stage, [21, i, sprite, points[id]]);

                    delete points[id];

                    return true;
                }
            }
        };


        function remove_line(stage, line) {

            var lines = stage.currentFrame.lines;

            for (var i = lines.length; i--;)
            {
                if (lines[i] === line)
                {
                    lines.splice(i, 1);
                    push_history(stage, [22, i, line]);

                    return true;
                }
            }
        };
    

        /**
         * 移除指定元素或线条
         * @param {*} item  要移除的元素或线条
         */
        this.remove = function (item) {

            if (item)
            {
                if (this.selectedItem === item)
                {
                    this.selectedItem = null;
                }

                return (item instanceof Line ? remove_line : remove_spirte)(this, item) || false;
            }

            return false;
        };


        /**
         * 清空战术板
         */
        this.clear = function () {

            var frames = this.frames,
                index = 0,
                any;

            while (any = frames[index++])
            {
                any.dispose();
            }

            this.pause();
            this.sprites.length = this.__offset = 0;

            if (any = this.historyStack)
            {
                any.length = any.index = 0;
            }

            splice.call(frames, 0);

            frames[0] = new Frame(this);
            frames.length = 1;

            this.index = 0;
        };


    });



    /**
     * 蓝球战术板类
     */
    exports.BasketBallPlaybook = Stage.extend(function (Class) {



        this.canvasWidth = 500;


        this.canvasHeight = 825;


        this.canvasHalfHeight = 475;



        this.__render_background = function () {

            var canvas = this.__background || (this.__background = this.canvas.cloneNode()),
                context = canvas.getContext('2d');

            context.save();

            try
            {
                var type = this.type,
                    height = type.indexOf('half') >= 0 ? 475 : 825;

                context.lineWidth = 2;
                context.fillStyle = '#F6C87C';
                context.fillRect(0, 0, 500, height);
    
                context.strokeStyle = '#FFFFFF';
                
                switch (type)
                {
                    case 'half-up':
                        draw_half(context);
                        break;

                    case 'half-down':
                        context.translate(0, 475);
                        context.scale(1, -1);
                        
                        draw_half(context);
                        break;

                    default:
                        draw_half(context);
                        context.stroke();

                        context.translate(0, 826);
                        context.scale(1, -1);
    
                        draw_half(context);
                        break;
                }
                
                context.stroke();
            }
            finally
            {
                context.restore();
            }

            return canvas
        };


        // 画球场的一半
        function draw_half(context) {

            var PI = Math.PI;
            
            context.beginPath();

            // 上大半圆
            context.moveTo(406, 63);
            context.arc(250, 100, 156, 0, PI);
            context.lineTo(94, 63);

            // 上蓝板
            context.moveTo(228, 93);
            context.lineTo(273, 93);

            // 上蓝圈
            context.moveTo(255, 100);
            context.arc(250, 100, 5, 0, PI * 2);

            // 上圆
            context.moveTo(295, 208);
            context.arc(250, 208, 45, 0, PI * 2);

            // 上梯形
            context.moveTo(175, 63);
            context.lineTo(205, 208);
            context.lineTo(295, 208);
            context.lineTo(325, 63);

            // 外框
            context.moveTo(63, 63);
            context.rect(63, 63, 375, 350);

            // 中圆
            context.moveTo(205, 413);
            context.arc(250, 413, 45, PI, 0);
        };



    });

 


});

